【SpringBoot】SpringBoot整合SpringSecurity+thymeleaf实现认证授权(配置对象版)

您所在的位置:网站首页 spring boot thymeleaf没有cookie 【SpringBoot】SpringBoot整合SpringSecurity+thymeleaf实现认证授权(配置对象版)

【SpringBoot】SpringBoot整合SpringSecurity+thymeleaf实现认证授权(配置对象版)

2024-01-06 13:16| 来源: 网络整理| 查看: 265

文章目录 一.概述1.框架概述2.环境准备 二.基本使用1.导入所需依赖2.创建配置对象3.初次访问4.配置登录用户5.退出当前登录6.开放内嵌框架7.指定登录页面8.开放静态资源9.指定退出页面 三.高级使用1.深入跨站请求伪造1.1.CSRF的概念1.2.CSRF的原理1.3.CSRF的防御1.4.form表单如何添加token1.5.ajax请求如何添加token 2.文件上传避免 CSRF 拦截3.如何关闭 CSRF 防御机制4.完成网站自动登录5.保存凭据到数据库6.展示当前登录用户7.对接数据库中数据8.用户密码进行加密9.动态展示功能菜单1.页面菜单动态展示2.业务代码动态拦截 10.权限不足异常处理11.保证当前登录人数12.开启或关闭CORS

一.概述 1.框架概述

Spring Security 是 Spring 家族中的一个安全管理框架,Spring Security 的两大核心功能就是认证(authentication)和授权(authorization)。

认证 :你是什么人。授权 :你能做什么。用户 :主要包括用户名称、用户密码和当前用户所拥有的角色信息,可用于实现认证操作。角色 :主要包括角色名称、角色描述和当前角色所拥有的权限信息,可用于实现授权操作。

常用词汇

认证 :authentication授权 :authorization用户 :user角色 :role登录 :login注销 :logout

权限管理需要三个对象

用户:主要包含用户名,密码和当前用户的角色信息,可实现认证操作。角色:主要包含角色名称,角色描述和当前角色拥有的权限信息,可实现授权操作。权限:权限也可以称为菜单,主要包含当前权限名称,url地址等信息,可实现动态展示菜单。 注:这三个对象中,用户与角色是多对多的关系,角色与权限是多对多的关系,用户与权限没有直接关系,二者是通过角色建立关联关系的。 2.环境准备

请在配套代码中,以及实现相关代码,直接拿来用就行了

源码地址

zhangsan:作为产品采购员,只能访问产品管理模块lisi:作为财务管理员,只能访问订单管理模块wangwu:作为系统管理员,可以访问所有模块,并可以对zhangsan和lisi进行访问权限管理 在这里插入图片描述

修改配置文件application.yml

spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/数据库名称 username: root password: 密码 二.基本使用 1.导入所需依赖

springboot版本

org.springframework.boot spring-boot-starter-parent 2.4.2

基础依赖

org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.4 mysql mysql-connector-java 5.1.49 org.springframework.boot spring-boot-starter-security 2.创建配置对象 @Configuration @EnableWebSecurity//开启Spring Security对WebMVC的支持 public class SecurityConfig extends WebSecurityConfigurerAdapter { //请将对Spring Security的配置方法写在这个类中 } 3.初次访问

完成上图操作后,就可以使用Spring Security的功能了,地址栏输入:http://localhost:8080

SpringBoot已经为SpringSecurity提供了默认配置,默认所有资源都必须认证通过才能访问。 在这里插入图片描述 默认的账户是user,而默认的密码必须看控制台: 在这里插入图片描述

在这里插入图片描述

4.配置登录用户 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("user").password("{noop}123456").roles("USER"); auth.inMemoryAuthentication().withUser("admin").password("{noop}123456").roles("ADMIN"); } 使用内存方式配置用户以及权限,角色前边千万不能加前缀ROLE_,否则会启动失败 5.退出当前登录

如果想要注销,访问:http://localhost:8080/logout就可以了,为了功能完整,请你打开main.html,第16行,修改注销地址为以下这段代码:

注销 6.开放内嵌框架

当你使用用户user密码123456登录的时候,默认就会进入到权限管理系统的后台首页,但是当你点击各个功能模块的时候,会发现localhost拒绝了我们的连接请求。其实这个问题还是挺常见的一个问题,项目中如果用到iframe嵌入网页,然后用到Spring Security,请求就会被拦截,如果你打开F12开发者控制台,你可能就会发现这样一句报错:Refused to display 'http://localhost:8080/user/add' in a frame because it set 'X-Frame-Options' to 'deny'.

在这里插入图片描述

Spring Security下,X-Frame-Options默认为DENY,非Spring Security环境下,X-Frame-Options的默认大多也是DENY,这种情况下,浏览器拒绝当前页面加载任何Frame页面,设置含义如下:

DENY:浏览器拒绝当前页面加载任何frame页面

SAMEORIGIN:frame页面的地址只能为同源域名下的页面

ALLOW-FROM:origin为允许frame加载的页面地址

方案如下:

关掉Spring Security对frame的拦截 @Override protected void configure(HttpSecurity http) throws Exception { //关闭X-Frame-Options响应头 http.headers().frameOptions().disable(); } 将X-Frame-Options设置为SAMEORIGIN,也就是只能是我们同域名下的请求访问,当然了,这种拦截机制肯定是为了保证系统的安全性,如果关掉了,有点太可惜了,这里采用第二种,而不是第一种的关闭。 @Override protected void configure(HttpSecurity http) throws Exception { //设置X-Frame-Options响应头为SAMEORIGIN http.headers().frameOptions().sameOrigin(); } 7.指定登录页面

想要使用自己的登录界面该怎么办?先打开源码,看看他是怎么写的,按照他的这个模式,我们模仿着写到自己的登录界面中不就好了 在这里插入图片描述 内置登录页面很简单,就是一个form表单,里边有两个文本框,一个是账号,一个是密码,还有最下边多了一个特殊的hidden隐藏域,这个隐藏域他是为了防止csrf跨站破坏的,这个值每一次启动项目都不一样,是一个动态值,他是为了标识当前请求一定是我们自己的请求,而不是别的网站仿造的请求,我们的所有请求都需要携带上这个标签上边的value值,我们也称这个值为token值,如果使用的是thymeleaf,那么form action会帮我们自动加上csrf 隐藏域,这样我们不用什么特殊处理也就可以登录了

我们找到我们工程中的login.html,里边是一个空的html,请把以下代码复制进入。下边是我们自己定义的一个登录页面。 DOCTYPE html> 自定义登录页 用户: 密码: 自动登录 登录

修改springSecurity配置类

@Override protected void configure(HttpSecurity http) throws Exception { //设置X-Frame-Options响应头为SAMEORIGIN http.headers().frameOptions().sameOrigin(); //放行不用权限的资源(去登录页面当然不需要用权限,否则你都看不到登录界面,还怎么登录,所以去登录界面必须放行) http.authorizeRequests().antMatchers("/toLogin").permitAll(); //拦截需要权限的资源(拦截所有请求,要想访问,登录的账号必须拥有USER和ADMIN的角色才行) http.authorizeRequests().antMatchers("/**").hasAnyRole("USER", "ADMIN").anyRequest().authenticated(); //设置自定义登录界面 http.formLogin()//启用表单登录 .loginPage("/login")//登录页面地址,只要你还没登录,默认就会来到这里 .loginProcessingUrl("/loginProcess")//登录处理程序,Spring Security内置控制器方法 .usernameParameter("username")//登录表单form中用户名输入框input的name名,不修改的话默认是username .passwordParameter("password")//登录表单form中密码框输入框input的name名,不修改的话默认是password .defaultSuccessUrl("/main")//登录认证成功后默认转跳的路径 //.successForwardUrl("/main")//登录成功跳转地址,使用的是请求转发 .failureForwardUrl("/login")//登录失败跳转地址,使用的是请求转发 .permitAll(); }

创建controller

@Controller public class MainController { @RequestMapping("/main") public String main() { return "main"; } //跳转到登录页的方法 @RequestMapping("/login") public String toLogin() { return "login"; } } 8.开放静态资源

Spring Security默认是拦截所有请求,那肯定也包括静态资源css、js、img之类的,因此,静态资源是应该要被放行的,静态资源是不需要进行保护的,我们需要在SecurityConfig配置如下代码来放行静态资源。

否则会导致前端资源加载失败 @Override public void configure(WebSecurity web) throws Exception { //配置不被拦截的系统资源 web.ignoring().antMatchers("/css/**"); web.ignoring().antMatchers("/img/**"); web.ignoring().antMatchers("/js/**"); web.ignoring().antMatchers("/favicon.ico"); web.ignoring().antMatchers("/error"); web.ignoring().antMatchers("/swagger-ui.html#/"); } 9.指定退出页面

当你现在想要退出登录,点击右上角咱们之前配置好的注销,你就会神奇的发现,好像不能退出了,这是因为,默认退出会直接跳转到/login自动生成的认证页面,现在,认证页面也就是登录页面,已经改成我们自己的登录页面了,你只要指定了登录页面了,那默认的登录页面自然就不会创建了,因此当你退出的时候也就会报404找不到异常。 在这里插入图片描述 修改springSecurity配置类

//设置自定义登出界面 http.logout()//启用退出登录 .logoutUrl("/logoutProcess")//退出处理程序,Spring Security内置控制器方法,(即前端登出请求地址) .logoutSuccessUrl("/login")//退出成功跳转地址 .invalidateHttpSession(true)//清除当前会话 .deleteCookies("JSESSIONID")//删除当前Cookie .permitAll(); //SpringSecurity3.2开始,默认会启动CSRF防护,一旦启动了CSRF防护,“/logout” 需要用post的方式提交,SpringSecurity才能过滤。

找到main.html,把之前的a标签的get请求,换成form的post请求,并加上隐藏域csrf,csrf不用我们自己加,只要你是用的thymeleaf的form,他会帮我们加上

三.高级使用 1.深入跨站请求伪造 1.1.CSRF的概念

CSRF跨站点请求伪造(Cross—Site Request Forgery),跟XSS攻击一样,存在巨大的危害性,你可以这样来理解:攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。

1.2.CSRF的原理

假设:其中Web A为存在CSRF漏洞的网站,Web B为攻击者构建的恶意网站,用户C为Web A网站的合法用户。

用户C打开浏览器,访问WEB A,输入用户名和密码请求登录网站WEB A;用户C在用户信息通过验证后,WEB A产生Cookie信息并返回给浏览器,此时用户登录WEB A成功,可以正常发送请求到WEB A;用户C未退出WEB A之前,在同一浏览器中,打开一个TAB页访问WEB B;WEB B接收到用户C请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点WEB A;浏览器在接收到这些攻击性代码后,根据WEB B的请求,在用户C不知情的情况下携带Cookie信息,向WEB A发出请求。WEB A并不知道该请求其实是由WEB B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自WEB B的恶意代码被执行。 1.3.CSRF的防御

目前防御 CSRF 攻击主要有三种策略

验证HTTP Referer字段在请求地址中添加 token 并验证(Spring Security采用)在 HTTP 头中自定义属性并验证。

1.验证 HTTP Referer 字段

HTTP 头字段 Referer记录了该 HTTP 请求的来源地址。正常情况下访问一个安全受限页面的请求来自于同一个网站

比如需要访问 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用户必须先登陆 bank.example,然后通过点击页面上的按钮来触发转账事件,该转帐请求的 Referer 值就会是转账按钮所在的页面的 URL,通常是以 bank.example 域名开头的地址。如果黑客要对银行网站实施 CSRF 攻击,他只能在他自己的网站构造请求,当用户通过黑客的网站发送请求到银行时,该请求的 Referer 是指向黑客自己的网站。因此,要防御 CSRF 攻击,银行网站只需要对于每一个转账请求验证其 Referer 值,如果是以 bank.example 开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果 Referer 是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。

优点: 简单易行,网站的普通开发人员不需要操心 CSRF 的漏洞,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查 Referer 的值就可以。特别是对于当前现有的系统,不需要改变当前系统的任何已有代码和逻辑,没有风险,非常便捷。

缺点:每个浏览器对于 Referer 的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不安全。事实上,对于某些浏览器,比如 IE6 或 FF2,目前已经有一些方法可以篡改 Referer 值。如果 bank.example 网站支持 IE6 浏览器,黑客完全可以把用户浏览器的 Referer 值设为以 bank.example 域名开头的地址,这样就可以通过验证,从而进行 CSRF 攻击。

2.在请求地址中添加 token 并验证

CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。

优点:

这种方法要比检查 Referer 要安全一些,token 可以在用户登陆后产生并放于 session 之中,然后在每次请求时把 token 从 session 中拿出,与请求中的 token 进行比对,但这种方法的难点在于如何把 token 以参数的形式加入请求。对于 GET 请求,token 将附在请求地址之后,这样 URL 就变成 http://url?csrftoken=tokenvalue。 而对于 POST 请求来说,要在 form 的最后加上 ,这样就把 token 以参数的形式加入请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上 token 是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用 javascript 遍历整个 dom 树,对于 dom 中所有的 a 和 form 标签后加入 token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html 代码,这种方法就没有作用,还需要程序员在编码时手动添加 token。

在Spring Security中,“GET”, “HEAD”, “TRACE”, "OPTIONS"四类请求可以直接通过,并不会被CsrfFilter过滤器过滤,会被直接放行,但是对于其他过滤器该过滤的还是会过滤的,除去上面四类,包括POST都要被验证携带token才能通过。

3.在 HTTP 头中自定义属性并验证

这种方法也是使用 token 并进行验证,和上一种方法不同的是,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。

然而这种方法的局限性非常大。XMLHttpRequest 请求通常用于 Ajax 方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对于没有进行 CSRF 防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为 XMLHttpRequest 请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。

1.4.form表单如何添加token

如果您使用的是thymeleaf,如果使用的是thymeleaf,那么form action会帮我们自动加上csrf 隐藏域,我们不用特殊处理。

如果自己想要设置,我们也可以使用隐藏域自己设置,一般我们不会设置这个,默认就有你设置他干啥,参考代码如下: 1.5.ajax请求如何添加token

如果您使用的是thymeleaf,则可以直接在head标签内加上一个隐藏域即可。

在这里插入图片描述

$(function () { var token = $("meta[name='_csrf']").attr("content"); var header = $("meta[name='_csrf_header']").attr("content"); $(document).ajaxSend(function(e, xhr, options) { xhr.setRequestHeader(header, token); }); }); 2.文件上传避免 CSRF 拦截

请将MultipartFilter在Spring Security过滤器之前指定。MultipartFilter在Spring Security过滤器之前指定,这意味着任何人都可以在您的服务器上放置临时文件。但是,只有授权用户才能提交由您的应用程序所处理的文件。通常,这是推荐的方法,因为临时文件上传对大多数服务器的影响可以忽略不计。具体配置代码如下:

public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer { @Override protected void beforeSpringSecurityFilterChain(ServletContext servletContext) { insertFilters(servletContext, new MultipartFilter()); } } 3.如何关闭 CSRF 防御机制 @Override protected void configure(HttpSecurity http) throws Exception { ... ... //关闭CSRF跨站点请求仿造保护 http.csrf().disable(); } 4.完成网站自动登录

如果我想要关闭浏览器,下次再打开浏览器,权限管理系统会自动根据我上次的登录状态进行登录,这就是登录常用的“自动登录功能”,要想实现自动登录功能,我们需要实现两处关键配置就能使用了,具体操作如下:

打开login.html修改自动登录的name为remember-me,这是一个默认名称,可以修改,但是一般我们就叫这个名 自动登录

配置 SecurityConfig 开启自动登录功能

@Override protected void configure(HttpSecurity http) throws Exception { ... ... //开启记住我功能(自动登录) http.rememberMe() .rememberMeParameter("remember-me")//表单参数名,默认参数是remember-me .rememberMeCookieName("remember-me")//浏览器存的cookie名,默认是remember-me .tokenValiditySeconds(60*60*24*30);//保存30两天,默认是两周 }

登录后关闭浏览器,然后重新打开 http://localhost:8080/ ,发现仍然可以访问,并且这时候不需要登录,他是怎么做到的呢?

其实,在登录成功以后会往当前网站的cookie中写入一个自动登录的token值,当我们下次启动的时候,只要这个cookie没有消失,Spring Security就能拿到这个cookie的中保存的token的值,然后帮我们自动登录认证。 在这里插入图片描述 5.保存凭据到数据库

自动登录功能方便是大家看得见的,但是安全性却令人担忧。因为cookie毕竟是保存在客户端的,很容易盗取,而且 cookie的值还与用户名、密码这些敏感数据相关,虽然加密了,但是将敏感信息存在客户端,还是不太安全。那么这就要提醒喜欢使用此功能的,用完网站要及时手动退出登录,清空认证信息。

此外,Spring Security还提供了remember-me的另一种相对更安全的实现机制:在客户端的cookie中,仅保存一个无意义的加密串(与用户名、密码等敏感数据无关),然后在数据库中保存该加密串-用户信息的对应关系,自动登录时,用cookie中的加密串,到数据库表中验证,如果通过,自动登录才算通过。这样,自动登录功能的安全性就有了保证

需要创建一张用于保存自动登录信息的表,这张表是固定的,包括名称、字段等信息,都不能修改,否则会认识失败。

CREATE TABLE `persistent_logins` ( `username` varchar(64) NOT NULL, `series` varchar(64) NOT NULL, `token` varchar(64) NOT NULL, `last_used` timestamp NOT NULL, PRIMARY KEY (`series`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

修改springSecurity配置类

@Override protected void configure(HttpSecurity http) throws Exception { ... ... //开启记住我功能(自动登录) http.rememberMe() .rememberMeParameter("remember-me")//表单参数名,默认参数是remember-me .rememberMeCookieName("remember-me")//浏览器存的cookie名,默认是remember-me .tokenValiditySeconds(60 * 60 * 24 * 30)//保存30两天,默认是两周 .tokenRepository(persistentTokenRepository());//使用数据库存储token,防止重启服务器丢失数据,非常重要,没有他不能保存到数据库 } //数据源是咱们默认配置的数据源,直接注入进来就行 @Autowired private DataSource dataSource; @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource); return jdbcTokenRepository; }

重新进行测试,发现也是可行的,并且这里给出了浏览器和数据库的截图信息: 在这里插入图片描述 在这里插入图片描述

6.展示当前登录用户

登录成功以后,如何显示出来当前登录成功的用户名呢?

有两种常用方法,他们都必须使用Spring Security的标签库,在使用thymeleaf渲染前端的html时,thymeleaf为SpringSecurity提供的标签属性,首先需要引入thymeleaf-extras-springsecurity5依赖支持。

1.引入依赖

  org.thymeleaf.extras   thymeleaf-extras-springsecurity5

2.在main.html文件里面导入标签所对应的名称空间。

DOCTYPE html>

第一种:打开main.html修改第12行

权限管理系统,您好:

第二种:打开 main.html 修改第12行

权限管理系统,您好:

在这里插入图片描述

7.对接数据库中数据

目前是在内存中(代码写死的就在内存中)配置好了两个用户(user、admin)以及他们所对应的角色

在真实场景中,我们就需要使用数据库来保存用户信息,我们如何对接数据库中的数据呢?

第一步:实现自己的 SysUserDetailsService 接口继承 UserDetailsService

public interface SysUserDetailsService extends UserDetailsService { }

第二步:实现自己的SysUserDetailsService接口的loadUserByUsername方法,方法传入一个字符串,代表当前登录的用户名

@Service @Transactional public class SysUserDetailsServiceImpl implements SysUserDetailsService { @Autowired private SysUserMapper sysUserMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //根据用户名去数据库中查询指定用户,这就要保证数据库中的用户的名称必须唯一,否则将会报错 SysUser sysUser = sysUserMapper.findUserByUsername(username); //如果没有查询到这个用户,说明数据库中不存在此用户,认证失败 if (sysUser == null) { throw new UsernameNotFoundException("user not exist"); } //获取该用户所对应的所有角色,当查询用户的时候级联查询其所关联的所有角色,用户与角色是多对多关系 //如果这个用户没有所对应的角色,也就是一个空集合,那么在登录的时候会报 403 没有权限异常,切记这点 List authorities = new ArrayList(); List sysRoles = sysUser.getSysRoles(); for (SysRole sysRole : sysRoles) { authorities.add(new SimpleGrantedAuthority(sysRole.getName())); } //最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证 //org.springframework.security.core.userdetails.User实现了UserDetails对象,是SpringSecurity内置认证对象 return new User(sysUser.getUsername(), "{noop}"+sysUser.getPassword(), authorities); } }

第三步:修改配置文件SecurityConfig中的 认证提供者换成咱们自己定义的

@Autowired private SysUserDetailsService sysUserDetailsServiceImpl; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(sysUserDetailsServiceImpl); }

第四步:使用数据库所提供的账户进行登录测试。 在这里插入图片描述

8.用户密码进行加密

第一步:配置加密对象,然后设置给咱们自己的认证提供者。

@Configuration @EnableWebSecurity//开启Spring Security对WebMVC的支持 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SysUserDetailsService sysUserDetailsServiceImpl; @Autowired private BCryptPasswordEncoder passwordEncoder; //........ //........ @Bean public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(sysUserDetailsServiceImpl).passwordEncoder(passwordEncoder); } }

第二步:保存用户的时候,给用户的密码进行加密,修改SysUserServiceImpl

@Autowired private BCryptPasswordEncoder passwordEncoder; @Override public void save(SysUser sysUser) { sysUser.setPassword(passwordEncoder.encode(sysUser.getPassword())); sysUserMapper.save(sysUser); }

第三步:去掉 SysUserDetailsServiceImpl 中的{noop}

@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //... //... //最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证 //org.springframework.security.core.userdetails.User实现了UserDetails对象,是SpringSecurity内置认证对象 return new User(sysUser.getUsername(), sysUser.getPassword(), authorities); }

第四步:手动修改数据库中的密码为加密后的密码,我们现在需要知道123456加密后的密文,需要手动生成

注意啊,调用BCryptPasswordEncoder 算法每一次生成都不一样,但是都可以用 public class CreatePwd { public static void main(String[] args) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String encode = bCryptPasswordEncoder.encode("123456"); System.out.println(encode); } }

第五步:重新登录权限管理系统,分别使用zhangsan、lisi、wangwu进行登录测试,都可以正常进行登录,但左侧的菜单右侧会报 403 没有权限

原因:

在进行数据库权限校验的时候,他会默认给你定义的角色加上ROLE_前缀,解决的方法就是给所有角色都加上前缀ROLE_

加完以后,你数据库中的效果应该如下: 在这里插入图片描述 修改完成以后,重新启动,然后分别登录,你将会看到如下截图: 在这里插入图片描述

9.动态展示功能菜单 1.页面菜单动态展示

使用Spring Security提供的标签库来动态判断,只有拥有指定角色的人,才可以访问我们指定的功能模块

具体做法如下,找到main.html进行修改:

产品管理 添加产品 产品列表 订单管理 添加订单 订单列表 用户管理 添加用户 用户列表 角色管理 添加角色 角色列表

在这里插入图片描述

2.业务代码动态拦截

假设一种场景,一个程序员,它使用zhangsan的账户登录系统后,闲来无事,他呢,自己又懂技术,想试试,在地址栏直接输入李四的订单页面,看看能不能进去,结果发现,进去了,这就是纰漏。

在这里插入图片描述 我们上一步所实现的只是表面你所看到的,也就是页面上实现了不同用户可以看到不同的菜单,但是在控制器层并没有拦截住,这就是导致问题的根本原因,一般我们的解决办法就是在业务层(控制器层也可以,但是不推荐),给相对应的方法或者相应的类添加角色判断注解,只有拥有相应角色的用户才能访问该方法或者该类

在Spring Security中,一共支持3种注解都可以做到这个效果,而这三种注解的开启都是一个注解上进行开启,我接下来会把三个注解都打开,只使用第一种注解,其余两种会给大家注释掉,要记住,打开的哪个注解,就用哪个注解来限制访问,必须配套使用。这里演示三类注解,实际开发中,用一类即可! @SpringBootApplication //三种任选其一,不必全开,全开也没事,一定要注意标签的对应关系 @EnableGlobalMethodSecurity( jsr250Enabled = true, //JSR-250注解 prePostEnabled = true, //spring表达式注解 securedEnabled = true //SpringSecurity注解,推荐使用 ) public class SpringBootSecurityApplication { public static void main(String[] args) { SpringApplication.run(SpringBootSecurityApplication.class, args); } }

修改OrderServiceImpl:我们就以这个类为例进行讲解,其余剩下的所有的实现都需要标注,可以在方法上标注注解,也可以在类上标注注解

@Service @Transactional public class OrderServiceImpl implements OrderService { ... ... @RolesAllowed({"ROLE_ADMIN", "ROLE_ORDER"})//JSR-250注解 //@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_ORDER')")//spring表达式注解 //@Secured({"ROLE_ADMIN", "ROLE_ORDER"})//SpringSecurity注解 @Override public void save(Order Order) { int size = orderMap.size(); int id = ++size; Order.setId(id); orderMap.put(id, Order); } @RolesAllowed({"ROLE_ADMIN", "ROLE_ORDER"})//JSR-250注解 //@PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_ORDER')")//spring表达式注解 //@Secured({"ROLE_ADMIN", "ROLE_ORDER"})//SpringSecurity注解 @Override public List findAll() { Collection Orders = orderMap.values(); return new ArrayList(Orders); } }

登录zhangsan,你再次输入lisi的添加订单地址,点击提交挺订单的时候,就会 403 权限不足,如果你连界面都不想展示出来 在这里插入图片描述

10.权限不足异常处理

每次权限不足都出现是Spring Boot自己生成的的403页面,很不友好,当出现403异常以后,如何跳转到我们自定义的页面

在解决问题之前,我们先定义自己的403没有权限的页面,以及通过控制器方法跳转到403.html,以上这几种情况还可以配置404、500等错误页面的跳转,如有需要也可以自行配置。

在 templates 目录中创建 error 目录,在 error 目录中创建 403.html

DOCTYPE html> 没有权限 403,没有权限

在 MainController 中添加跳转方法,代码如下:

//跳转到错误页的方法 @RequestMapping("/to403") public String to403() { return "error/403"; }

以下几种方法任选其一使用即可,不必全部配置,推荐使用第二种Spring MVC提供的异常处理机制。

第一种: 在 SecurityConfig中配置一下代码即可

@Override protected void configure(HttpSecurity http) throws Exception { // ... // ... //异常处理,使用函数表达式的写法可以不用在单独写一个类,非常方便 http.exceptionHandling() .accessDeniedHandler((request, response, ex) -> { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setHeader("Content-Type", "application/json;charset=utf-8"); PrintWriter out = response.getWriter(); out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}"); out.flush(); out.close(); }); }

在这里插入图片描述

第二种: 创建一个包 advice ,然后创建 ExceptionAdvice

@ControllerAdvice public class ExceptionAdvice { //别导错类了:org.springframework.security.access.AccessDeniedException //只有出现AccessDeniedException异常才调转403.html页面 @ExceptionHandler(AccessDeniedException.class) public String exceptionAdvice() { return "forward:/to403"; } }

在这里插入图片描述

11.保证当前登录人数 @Override protected void configure(HttpSecurity http) throws Exception { //........ //1、保证当前登录人数——单用户登录,如果有一个登录了,同一个用户在其他地方登录将前一个剔除下线 //maximumSessions 表示配置最大会话数为 1,这样后面的登录就会自动踢掉前面的登录 //http.sessionManagement().maximumSessions(1).expiredUrl("/login"); //2、 保证当前登录人数——单用户登录,如果有一个登录了,同一个用户在其他地方不能登录,禁止新的登录 http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true); } 12.开启或关闭CORS @Override protected void configure(HttpSecurity http) throws Exception { // ... // ... //开启CORS http.cors(); //关闭CORS // http.cors().disable(); }

【第一篇】SpringSecurity的初次邂逅 SpringSecurity常用过滤器介绍 SpringSecurity认证流程分析 SpringSecurity实现数据库认证

SpringSecurity详细介绍RememberMe功能 SpringSecurity详细介绍RememberMe源码流程 SpringSecurity认证专题之【AuthenticationManager】 详细介绍OAuth2.0及实现和SpringSecurity的整合应用



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3